/*
 *  (C) Copyright 2008 Insula Tecnologia da Informacao Ltda.
 * 
 *  This file is part of Vulcano.
 *
 *  Vulcano is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Vulcano is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Vulcano.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.openinsula.vulcano.flex.controller.filters;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.hibernate.Hibernate;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

@Component
@Aspect
public class LazySafeControllerFilter {

	private static final Log logger = LogFactory.getLog(LazySafeControllerFilter.class);

	private final ReflectionUtils.FieldFilter fieldFilter = new ReflectionUtils.FieldFilter() {

		@Override
		public boolean matches(Field field) {
			return field.isAnnotationPresent(OneToMany.class) || field.isAnnotationPresent(OneToOne.class)
					|| field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(ManyToMany.class);
		}

	};

	@AfterReturning(pointcut = "@target(org.openinsula.vulcano.flex.controller.annotations.LazySafeController) && execution(public * *(..))", returning = "proxy")
	public void removeLazy(final JoinPoint jp, final Object proxy) {

		if (logger.isDebugEnabled()) {
			String msg = String.format("method [%s] from controller [%s] intercepted. Returned value: %s", jp
					.getSignature().getName(), jp.getSignature().getDeclaringTypeName(), proxy);

			logger.debug(msg);
		}

		doRemoveLazy(proxy, new HashSet<Object>());
	}

	private void doRemoveLazy(final Object proxy, final Set<Object> initializedSet) {
		if (logger.isDebugEnabled()) {
			logger.debug("Ignoring lazy associations for object " + proxy);
		}

		if (proxy instanceof Collection) {
			if (logger.isDebugEnabled()) {
				logger.debug("object is a collection. Resolving items recursively");
			}

			Collection<?> proxyList = (Collection<?>) proxy;

			for (Object object : proxyList) {
				doRemoveLazy(object, initializedSet);
			}

		}
		else if (proxy != null && initializedSet.add(proxy)) {
			ReflectionUtils.doWithFields(proxy.getClass(), new ReflectionUtils.FieldCallback() {

				@Override
				public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {
					ReflectionUtils.makeAccessible(field);
					Object fieldValue = field.get(proxy);

					if (fieldValue != null) {
						boolean initialized = Hibernate.isInitialized(fieldValue);

						if (initialized) {
							doRemoveLazy(fieldValue, initializedSet);

						}
						else {
							field.set(proxy, null);
						}

						if (logger.isDebugEnabled()) {
							logger.debug(String.format("%s %s, instance[%s]", (initialized ? "INITIALIZED" : "NULL"),
									field, proxy));
						}
					}
				}

			}, fieldFilter);

		}
	}

}
